#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <alsa/asoundlib.h>

#define PCM_FORMAT SND_PCM_FORMAT_S16_LE // PCM格式：16位小端
#define PERIOD_SIZE 2048                 // 设置周期大小为2048帧
#define BUFFER_SIZE (PERIOD_SIZE * 4)    // 设置缓冲区大小为周期大小的4倍

snd_pcm_t *playback = NULL;             // 全局PCM播放句柄
FILE *pcm_file = NULL;                  // 全局PCM输入文件
char *buffer = NULL;                    // 全局缓冲区
snd_pcm_uframes_t frames = PERIOD_SIZE; // 全局缓冲区帧数

/**
 * 清理资源并安全退出程序
 * 释放分配的缓冲区、关闭文件和PCM设备
 */
void cleanup()
{
    if (buffer)
    {
        free(buffer); // 释放缓冲区
        buffer = NULL;
    }
    if (pcm_file)
    {
        fclose(pcm_file); // 关闭文件
        pcm_file = NULL;
    }
    if (playback)
    {
        snd_pcm_drain(playback); // 清空PCM播放缓冲区
        snd_pcm_close(playback); // 关闭PCM设备
        playback = NULL;
    }
    printf("Playback Complete!\n");
}

/**
 * 信号处理函数
 * 用于捕捉信号并调用cleanup函数清理资源
 * @param sig: 捕捉到的信号编号
 */
void signal_handler(int sig)
{
    cleanup();
    exit(0);
}

/**
 * 配置PCM设备参数
 * 设置PCM设备的硬件参数，包括访问类型、数据格式、声道数、采样率、周期大小和缓冲区大小
 * @param pcm_device: PCM设备名称
 * @param channels: 声道数
 * @param sample_rate: 采样率
 * @return: 成功返回0，失败返回负错误码
 */
int configure_pcm_device(const char *pcm_device, unsigned int channels, unsigned int sample_rate)
{
    int err;
    snd_pcm_hw_params_t *params;
    unsigned int rate = sample_rate;
    int dir;
    unsigned int actual_rate;

    // 打开PCM设备用于音频播放
    if ((err = snd_pcm_open(&playback, pcm_device, SND_PCM_STREAM_PLAYBACK, 0)) < 0)
    {
        fprintf(stderr, "Error opening PCM device %s: %s\n", pcm_device, snd_strerror(err));
        return err;
    }

    // 分配硬件参数对象
    snd_pcm_hw_params_alloca(&params);

    // 初始化硬件参数结构体的默认值
    if ((err = snd_pcm_hw_params_any(playback, params)) < 0)
    {
        fprintf(stderr, "Cannot initialize hardware parameter structure: %s\n", snd_strerror(err));
        return err;
    }

    // 设置访问类型为交错模式
    if ((err = snd_pcm_hw_params_set_access(playback, params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
    {
        fprintf(stderr, "Error setting access type: %s\n", snd_strerror(err));
        return err;
    }

    // 设置PCM数据格式
    if ((err = snd_pcm_hw_params_set_format(playback, params, PCM_FORMAT)) < 0)
    {
        fprintf(stderr, "Error setting format: %s\n", snd_strerror(err));
        return err;
    }

    // 设置声道数
    if ((err = snd_pcm_hw_params_set_channels(playback, params, channels)) < 0)
    {
        fprintf(stderr, "Error setting channels: %s\n", snd_strerror(err));
        return err;
    }

    // 设置采样率
    if ((err = snd_pcm_hw_params_set_rate_near(playback, params, &rate, &dir)) < 0)
    {
        fprintf(stderr, "Error setting sample rate: %s\n", snd_strerror(err));
        return err;
    }

    // 设置周期大小（帧数）
    if ((err = snd_pcm_hw_params_set_period_size_near(playback, params, &frames, &dir)) < 0)
    {
        fprintf(stderr, "Error setting period size: %s\n", snd_strerror(err));
        return err;
    }

    // 设置缓冲区大小
    snd_pcm_uframes_t buffer_size = BUFFER_SIZE;
    if ((err = snd_pcm_hw_params_set_buffer_size_near(playback, params, &buffer_size)) < 0)
    {
        fprintf(stderr, "Error setting buffer size: %s\n", snd_strerror(err));
        return err;
    }

    // 设置硬件参数
    if ((err = snd_pcm_hw_params(playback, params)) < 0)
    {
        fprintf(stderr, "Error setting hardware parameters: %s\n", snd_strerror(err));
        return err;
    }

    // 获取实际的周期大小（帧数）
    snd_pcm_hw_params_get_period_size(params, &frames, &dir);

    // 获取实际的采样率
    snd_pcm_hw_params_get_rate(params, &actual_rate, &dir);

    // 为播放数据分配缓冲区
    buffer = (char *)malloc(snd_pcm_frames_to_bytes(playback, frames));
    if (!buffer)
    {
        fprintf(stderr, "Cannot allocate buffer\n");
        return -ENOMEM;
    }

    // 打印当前参数
    printf("PCM Device: %s\n", pcm_device);
    printf("Channels: %u\n", channels);
    printf("Sample Rate: %u Hz\n", actual_rate);
    printf("Period Size: %lu frames\n", frames);

    return 0;
}

/**
 * 打开输入文件
 * @param filename: 输入文件名称
 * @return: 成功返回0，失败返回负错误码
 */
int open_input_file(const char *filename)
{
    pcm_file = fopen(filename, "rb");
    if (!pcm_file)
    {
        fprintf(stderr, "Cannot open input file\n");
        return -ENOENT;
    }
    return 0;
}

/**
 * 开始播放
 * @return: 成功返回0，失败返回负错误码
 */
int start_playback()
{
    while (1)
    {
        size_t read_size = fread(buffer, 1, snd_pcm_frames_to_bytes(playback, frames), pcm_file);
        if (read_size == 0)
        {
            break; // 文件结束
        }

        int pcm;
        while ((pcm = snd_pcm_writei(playback, buffer, snd_pcm_bytes_to_frames(playback, read_size))) < 0)
        {
            if (pcm == -EPIPE)
            {
                fprintf(stderr, "Underrun occurred\n");
                snd_pcm_prepare(playback); // 准备PCM设备
            }
            else if (pcm < 0)
            {
                fprintf(stderr, "Error writing to PCM device: %s\n", snd_strerror(pcm));
                return pcm;
            }
        }

        if (pcm != (int)snd_pcm_bytes_to_frames(playback, read_size))
        {
            fprintf(stderr, "Short write: wrote %d frames\n", pcm);
        }
    }
    return 0;
}

/**
 * 初始化音频播放
 * 设置PCM设备和输入文件，并开始播放
 * @param pcm_device: PCM设备名称
 * @param channels: 声道数
 * @param sample_rate: 采样率
 * @param filename: 输入文件名称
 * @return: 成功返回0，失败返回负错误码
 */
int init_audio_playback(const char *pcm_device, unsigned int channels, unsigned int sample_rate, const char *filename)
{
    int err;

    // 注册信号处理程序，用于清理资源并安全退出
    signal(SIGINT, signal_handler);

    // 配置PCM设备参数
    if ((err = configure_pcm_device(pcm_device, channels, sample_rate)) < 0)
    {
        cleanup();
        return err;
    }

    // 打开输入文件
    if ((err = open_input_file(filename)) < 0)
    {
        cleanup();
        return err;
    }

    printf("Playing... Press Ctrl+C to stop.\n");

    // 开始播放
    err = start_playback();
    if (err < 0)
    {
        cleanup();
        return err;
    }

    cleanup();
    return 0;
}

int main()
{
    // 使用指定的PCM设备、声道数、采样率和输入文件名初始化音频播放
    int err = init_audio_playback("hw:0,0", 2, 44100, "output.pcm");
    if (err < 0)
    {
        fprintf(stderr, "Audio playback initialization failed: %s\n", snd_strerror(err));
        return err;
    }

    return 0;
}
